/**     
* 用于加载多张图片  
* @param context 2D绘图对象     
* @param onSuccess 判断加载绘制是否成功        
*/
function loadMultiImage(list, onSuccess) {
    let current = 0;
    const total = list.length;
    for (const item of list) {
        loadImage(item, function () {
            current++;
            if (current == total) {
                onSuccess();
            }
        })
    }
}

/**     
* 获取图片url  
* @param url 图片地址           
*/
function getImage(url) {
    return images[url]
}

function getAudio(url){
    return audios[url]
}

function getText(url) {
    return texts[url]
}

function loadaudio(url:string,callback:Function){
    const mp3 = new Audio(url)
    audios[url]= mp3;
    callback();
}


/**     
*加载单张图片
* @param url 图片地址
* @param callback 回调           
*/
function loadImage(url, callback) {
    const image = new Image();
    image.src = url;
    image.onload = function () {
        images[url] = image;
        callback();
    }
}

function loadText(url: string, onSuccess: Function) {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.send();
    xhr.onload = () => {
        const text = xhr.responseText;
        texts[url] = text;
        onSuccess();
    }
}

function loadMultiAudio(list, callBack){
    let current = 0;
    const total = list.length;
    for(const item of list){
        loadaudio(item,function(){
            current++;
            if(current==total){
                callBack();
            }
        })
    }
}

function loadMultiText(list, onSuccess) {
    let current = 0;
    const total = list.length;
    for (const item of list) {
        loadText(item, function () {
            current++;
            if (current == total) {
                onSuccess();
            }
        })
    }
}

function dataToBehaviour(behaviour: Behaviour, data) {
    const properties = behaviour["constructor"]["properties"];
    if (properties) {
        for (let p of properties) {
            const key = p.key;
            const defaultValue = p.defaultValue;
            if (data[key]) {
                behaviour[key] = data[key];
            } else {
                behaviour[key] = defaultValue;
            }
        }
    }
}

function behaviourToData(behaviour: Behaviour) {
    const name = behaviour["constructor"].name;
    const properties = behaviour["constructor"]["properties"];

    const obj: any = {
        name: name
    }
    if (properties) {
        for (let p of properties) {
            const value = behaviour[p.key];
            if (value !== p.defaultValue) {
                obj[p.key] = value;
            }
        }
    }
    return obj;
}

class GameEngineCore {

    root: GameObject;

    renderList = [];

    ids = {};

    behaviourClasses = {};

    systems: GameEngineSystem[] = [];

    configData: {
        scripts: number[],
        scenes: string[],
        images: string[],
        audios:string[]
    }

    loadAllResource(callback: Function) {
        loadMultiText(core.configData.scenes, () => {
            loadMultiImage(core.configData.images, ()=>{
                loadMultiAudio(core.configData.audios,callback)
            });
        })
    };

    start(callback: Function) {
        this.configData = window["configData"];
        this.root = new GameObject();
        this.root.id = "root"
        this.root.addBehaviour(new Transform());
        this.loadAllResource(callback);
        requestAnimationFrame(this.onFrame.bind(this));
    }
    changeScene(sceneOrPrefabUrl: string) {
        const text = getText(sceneOrPrefabUrl);
        const json = JSON.parse(text);
        this.root.removeAllChildren();
        const container = core.createFromData(json);
        this.root.addChild(container);
        this.dispatchSystemEvent("sceneChanged")
    }

    dispatchSystemEvent(eventName: string) {
        for (let system of this.systems) {
            system.onRecieveEvent(eventName);
        }
    }

    toData(gameObject: GameObject) {
        const obj: any = {
        }
        if (gameObject.id) {
            obj.id = gameObject.id;
        }
        if (gameObject.prefabUrl) {
            obj.prefabUrl = gameObject.prefabUrl;
            return obj;
        }
        if (gameObject.children.length > 0) {
            const children = gameObject.children.map(child => this.toData(child));
            obj.children = children;
        }
        const behaviours = gameObject.behaviours.map(behaviour => behaviourToData(behaviour));
        obj.behaviours = behaviours;


        return obj;
    }

    createPrefab(prefabUrl: string) {
        const text = getText(prefabUrl);
        const json = JSON.parse(text);
        const gameObject = this.createFromData(json);
        gameObject.prefabUrl = prefabUrl;
        return gameObject;
    }

    createFromData(dataName) {

        let dataObj: GameObject;
        if (dataName.prefabUrl) {
            dataObj = core.createPrefab(dataName.prefabUrl);
        } else {
            dataObj = new GameObject();
            if (dataName.behaviours) {
                for (let behaviourData of dataName.behaviours) {
                    const BehaviourClass = core.getBehaviourClass(behaviourData.name);
                    const behaviour = new BehaviourClass();
                    dataToBehaviour(behaviour, behaviourData);
                    dataObj.addBehaviour(behaviour);

                }
            }
            if (dataName.children) {
                for (let childData of dataName.children) {
                    const child = core.createFromData(childData);
                    dataObj.addChild(child);
                }
            }
        }
        if (dataName.id) {
            this.ids[dataName.id] = dataObj;
            dataObj.id = dataName.id;
        }
        return dataObj;
    }

    getObjectById(id) {
        return this.ids[id];
    }

    registerBehaviour(behaviourClass) {
        const name = behaviourClass.name;
        this.behaviourClasses[name] = behaviourClass;

    }

    getBehaviourClass(behaviourClassName) {
        const behaviourClass = this.behaviourClasses[behaviourClassName];
        return behaviourClass;
    }

    getAllBehaviourClassName() {
        const result = [];
        for (let key in this.behaviourClasses) {
            result.push(key)
        }
        return result;
    }

    registerSystem(system) {
        this.systems.push(system);
    }

    onFrame(advancedTime) {
        context.save();
        context.clearRect(0, 0, canvas.width, canvas.height)

        for (let system of this.systems) {
            system.onFrame(advancedTime);
        }

        context.restore();
        requestAnimationFrame(this.onFrame.bind(this));
    }

}
const core = new GameEngineCore();